home *** CD-ROM | disk | FTP | other *** search
- #
- # numplace.pl : ナンバープレース自動解法
- #
- # Copyright (C) 1999 by Makoto Hiroi
- #
-
- # 外部変数定義
- @board = (); # 盤面(2次元配列)
- @postion = (); # 確定していない場所を格納
- @biton = ();
- @bitoff = ();
- @xflag = (0x3fe) x 9;
- @yflag = (0x3fe) x 9;
- @gflag = (
- [0x3fe,0x3fe,0x3fe],
- [0x3fe,0x3fe,0x3fe],
- [0x3fe,0x3fe,0x3fe]
- );
- @group = (0,0,0,1,1,1,2,2,2); # グループ
- @group_s = (0,0,0,3,3,3,6,6,6); # スタート位置
-
- # 初期化
- sub init_bit_table {
- my $i;
- for( $i = 1; $i <= 9; $i++ ){
- $biton[$i] = 1 << $i;
- $bitoff[$i] = 0x3fe - $biton[$i];
- }
- }
-
- # 解答の出力
- sub print_board {
- foreach $a (@board) {
- print "@$a\n";
- }
- print "\n";
- }
-
- # 数字を書く
- sub write_number {
- my ($n, $x, $y) = @_;
- my $off = $bitoff[$n];
- $board[$x][$y] = $n;
- $xflag[$x] &= $off;
- $yflag[$y] &= $off;
- $gflag[ $group[$x] ][ $group[$y] ] &= $off;
- }
-
- # 数字を消す
- sub delete_number {
- my ($n, $x, $y) = @_;
- my $on = $biton[$n];
- $board[$x][$y] = 0;
- $xflag[$x] |= $on;
- $yflag[$y] |= $on;
- $gflag[ $group[$x] ][ $group[$y] ] |= $on;
- }
-
- # ビットを求める
- sub get_bit {
- my ($x, $y) = @_;
- $xflag[$x] & $yflag[$y] & $gflag[ $group[$x] ][ $group[$y] ];
- }
-
- # ビットが一つで確定できるか
- sub kakutei_1 {
- my $i;
- for( $i = 0; $i < @postion; ){
- my $x = $postion[$i++];
- my $y = $postion[$i++];
- my $f = &get_bit( $x, $y );
- if( $f ){
- my $n;
- for( $n = 1; $n <= 9; $n++ ){
- if( $f == $biton[$n] ){
- &write_number( $n, $x, $y );
- }
- }
- }
- }
- }
-
- # 縦の確定
- sub kakutei_x {
- my ($x,$y,$n);
- for( $x = 0; $x < 9; $x++ ){
- next if $xflag[$x] == 0; # 全ての数字が埋まっている
- for( $n = 1; $n <= 9; $n++ ){
- my ($c,$p) = (0,0);
- next unless ($xflag[$x] & $biton[$n]);
- for( $y = 0; $y < 9; $y++ ){
- if( $board[$x][$y] == 0 &&
- (&get_bit( $x, $y ) & $biton[$n]) ){
- $c++;
- $p = $y;
- }
- }
- &write_number( $n, $x, $p ) if $c == 1;
- }
- }
- }
-
- # 横の確定
- sub kakutei_y {
- my ($x,$y,$n);
- for( $y = 0; $y < 9; $y++ ){
- next if $yflag[$y] == 0; # 全ての数字が埋まっている
- for( $n = 1; $n <= 9; $n++ ){
- my ($c,$p) = (0,0);
- next unless ($yflag[$y] & $biton[$n]);
- for( $x = 0; $x < 9; $x++ ){
- if( $board[$x][$y] == 0 &&
- (&get_bit( $x, $y ) & $biton[$n]) ){
- $c++;
- $p = $x;
- }
- }
- &write_number( $n, $p, $y ) if $c == 1;
- }
- }
- }
-
- # グループの確定
- sub kakutei_g {
- my ($x,$y,$n);
- for( $x = 0; $x < 9; $x += 3 ){
- for( $y = 0; $y < 9; $y += 3 ){
- next if $gflag[ $group[$x] ][ $group[$y] ] == 0; # 全ての数字が埋まっているよ
- for( $n = 1; $n <= 9; $n++ ){
- next unless ($gflag[ $group[$x] ][ $group[$y] ] & $biton[$n]);
- my ($c,$x1,$y1,$i,$j);
- $c = 0;
- for( $i = 0; $i < 3; $i++ ){
- for( $j = 0; $j < 3; $j++ ){
- if( $board[$x + $i][$y + $j] == 0 &&
- (&get_bit($x + $i, $y + $j) & $biton[$n]) ){
- $c++;
- $x1 = $x + $i;
- $y1 = $y + $j;
- }
- }
- }
- &write_number( $n, $x1, $y1 ) if $c == 1;
- }
- }
- }
- }
-
- # マスのある縦、横、枠をまとめてチェックする
- sub kakutei_search_1 {
- while( 1 ){
- # 確定1
- &kakutei_1();
- # 確定2
- &kakutei_x();
- &kakutei_y();
- &kakutei_g();
- # チェック
- my ($i, $j);
- for( $i = 0; $i < @postion; ){
- my $x = $postion[$i++];
- my $y = $postion[$i++];
- if( $board[$x][$y] == 0 ){
- $postion[$j++] = $x;
- $postion[$j++] = $y;
- }
- }
- if( $j == @postion ){
- # 確定できなかった
- return 0
- } elsif( $j == 0 ){
- # 確定したよ
- return 1;
- }
- $#postion = $j - 1;
- }
- 1;
- }
-
- #
- # バックトラックによる解析
- # 再帰が深くなるとバスエラーが発生する
- #
- sub search {
- my $pos = shift;
- my ($x, $y, $f);
- if( $pos == @postion ){
- # 答えが見つかった
- print_board();
- return;
- }
- $x = $postion[$pos];
- $y = $postion[$pos + 1];
- $f = &get_bit( $x, $y );
- if( $f ){
- my $i;
- for( $i = 1; $i <= 9; $i++ ){
- if( $f & $biton[$i] ){
- &write_number( $i, $x, $y );
- &search( $pos + 2 );
- &delete_number( $i, $x, $y );
- }
- }
- }
- }
-
- #
- # バックトラックを繰り返しに変換
- #
- sub search_1 {
- my $pos = 0;
- my @save_number = ();
- my ($x, $y, $f, $i);
- while( $pos >= 0 ){
- if( $pos == @postion ){
- # 答えが見つかった
- print_board();
- # 一つ見つけたら終了
- return;
- }
- $x = $postion[$pos];
- $y = $postion[$pos + 1];
- if( $board[$x][$y] == 0 ){
- # 新しい数値をセット
- $i = 1;
- $f = &get_bit( $x, $y );
- } else {
- # 数字の選び直し
- $i = $save_number[$pos];
- $f = $save_number[$pos + 1];
- &delete_number( $i, $x, $y );
- $i++;
- }
- for( ; $i <= 9; $i++ ){
- if( $f & $biton[$i] ){
- # 数字をセット */
- &write_number( $i, $x, $y );
- # データセーブ
- $save_number[$pos] = $i;
- $save_number[$pos + 1] = $f;
- last;
- }
- }
- if( $board[$x][$y] > 0 ){
- # 数字をセットできたので次の位置へ
- $pos += 2;
- } else {
- # 元に戻る
- $pos -= 2;
- }
- }
- }
-
- # データチェック
- sub data_check {
- if( @postion == 0 ){
- die "空いているマスがありません\n";
- }
- my $i = 0;
- while( $i < @postion ){
- my $x = $postion[$i++];
- my $y = $postion[$i++];
- unless( &get_bit( $x, $y ) ){
- die "($x,$y) に数字を置くことができません\n";
- }
- }
- }
-
- # 解析処理
- sub analysis {
- my ($x, $y);
- for( $x = 0; $x < 9; $x++ ){
- for( $y = 0; $y < 9; $y++ ){
- my $n = $board[$x][$y];
- if( $n > 0 ){
- if( &get_bit( $x, $y ) & $biton[$n] ){
- &write_number( $n, $x, $y );
- } else {
- &print_board();
- die "エラー:($x,$y) の数字 $n が矛盾している\n";
- }
- } else {
- push( @postion, $x );
- push( @postion, $y );
- }
- }
- }
- &data_check();
- }
-
- # データリード
- sub read_data {
- my $filename = shift;
- open(IN,$filename) or die "ファイル $filename が見つかりません\n";
- while( <IN> ){
- # コメントと空行のチェック
- next if( /^[;#]/ || /^\s*$/ );
- my @item = split;
- if( @item != 9 || grep( ($_ < 0 || $_ > 9), @item ) ){
- die "エラーデータ : $_ 1行に 0 - 9 の数字を 9 個並べてください\n";
- }
- push( @board, [ @item] );
- }
- if( @board != 9 ){
- die "9 行のデータが必要です\n";
- }
- }
-
- # ***** メイン *****
-
- # 初期化
- &init_bit_table();
-
- &read_data( shift );
-
- # 解析
- &analysis();
- print "***** 確定サーチ中 *****\n\n";
- if( &kakutei_search_1() ){
- &print_board();
- } else {
- print "***** 探索中 *****\n\n";
- # &search( 0 );
- &search_1();
- }
-
- # end of file
-